package com.hgd.security.evp.common.config;

import com.hgd.security.evp.common.annotation.HgdKeyGenerator;
import com.hgd.security.evp.common.util.EmptyUtil;
import com.hgd.security.evp.common.util.PackageUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

import javax.persistence.Id;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.List;

/**************************************
 * Copyright (C), Navinfo
 * Package: 
 * @Author: 尚村山夫
 * @Date: Created in 2019/8/2 13:24
 * @Description:
 **************************************/
//@Component
public class KeyGeneratorConfig implements CommandLineRunner {

    @Autowired
    private SqlSession sqlSession;

    @Value("${mybatis.entity-package}")
    private String entityPackage;

    @Override
    public void run(String... strings) throws Exception {
        try {
            changeMappedStatement();
        } catch (IOException | ClassNotFoundException | InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    private void changeMappedStatement()
        throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
        org.apache.ibatis.session.Configuration configuration = sqlSession.getConfiguration();

        if (StringUtils.isNotBlank(entityPackage)) {
            List<String> classNameList = PackageUtil.getClassName(entityPackage);
            if (EmptyUtil.isNotEmpty(classNameList)) {
                for (String className : classNameList) {
                    Class<?> clazz = Class.forName(className);
                    HgdKeyGenerator hgdKeyGenerator = clazz.getAnnotation(HgdKeyGenerator.class);

                    if (null != hgdKeyGenerator) {
                        // 说明entity实体有需求返回主键
                        boolean flag = false;

                        // 主键字段
                        Field idField = null;
                        Field[] declaredFields = clazz.getDeclaredFields();

                        // 判断是否存在主键列
                        for (Field declaredField : declaredFields) {
                            Id idAnnotation = declaredField.getAnnotation(Id.class);
                            HgdKeyGenerator hgdKeyGeneratorInIdColumn =
                                declaredField.getAnnotation(HgdKeyGenerator.class);
                            if (idAnnotation != null || hgdKeyGeneratorInIdColumn != null) {
                                idField = declaredField;
                                flag = true;
                                break;
                            }
                        }

                        if (flag) {
                            // 获取旧MappedStatement
                            MappedStatement oldMappedStatement =
                                configuration.getMappedStatement(hgdKeyGenerator.method());
                            // 当插入操作的方法全路径名填写错误时，可能导致oldMappedStatement==null

                            MappedStatement newMappedStatement = null;
                            if (null != oldMappedStatement) {
                                // 此时已满足返回生成主键条件
                                newMappedStatement =
                                    generateNewMappedStatement(oldMappedStatement, hgdKeyGenerator, idField);
                                changeMappedStatement(configuration, hgdKeyGenerator.method(), oldMappedStatement,
                                    newMappedStatement);
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * 移除configuration中oldMappedStatement
     * @param configuration 移除configuration中oldMappedStatement
     * @param method 待移除的key
     * @param oldMappedStatement 待移除的对象
     */
    private void changeMappedStatement(org.apache.ibatis.session.Configuration configuration, String method,
        MappedStatement oldMappedStatement, MappedStatement newMappedStatement) {
        Collection<String> mappedStatementNames = configuration.getMappedStatementNames();
        Collection<MappedStatement> mappedStatements = configuration.getMappedStatements();

        if(EmptyUtil.isNotEmpty(mappedStatementNames)){
            mappedStatementNames.remove(method);
        }

        if(EmptyUtil.isNotEmpty(mappedStatements)){
            mappedStatements.remove(oldMappedStatement);
        }

        configuration.addMappedStatement(newMappedStatement);
    }

    private MappedStatement generateNewMappedStatement(MappedStatement mappedStatement, HgdKeyGenerator hgdKeyGenerator,
        Field idField) throws IllegalAccessException, InstantiationException {

        String id = mappedStatement.getId();
        MappedStatement.Builder statementBuilder =
            new MappedStatement.Builder(mappedStatement.getConfiguration(), id, mappedStatement.getSqlSource(),
                mappedStatement.getSqlCommandType());

        Class<?> aClass = hgdKeyGenerator.keyGenerator();
        Object o = aClass.newInstance();
        KeyGenerator keyGenerator = (KeyGenerator) o;

        statementBuilder.resource(mappedStatement.getResource()).fetchSize(mappedStatement.getFetchSize())
            .timeout(mappedStatement.getTimeout()).statementType(mappedStatement.getStatementType())
            .keyGenerator(keyGenerator).keyProperty(idField.getName())
            .keyColumn(StringUtils.join(mappedStatement.getKeyColumns(), ","))
            .databaseId(mappedStatement.getDatabaseId()).lang(mappedStatement.getLang())
            .resultOrdered(mappedStatement.isResultOrdered())
            .resultSets(StringUtils.join(mappedStatement.getResultSets(), ","))
            .resultMaps(mappedStatement.getResultMaps()).resultSetType(mappedStatement.getResultSetType())
            .flushCacheRequired(mappedStatement.isFlushCacheRequired()).useCache(mappedStatement.isUseCache())
            .cache(mappedStatement.getCache()).parameterMap(mappedStatement.getParameterMap());

        return statementBuilder.build();
    }
}
